// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TaskDebugger.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the TaskDebugger type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading.Mocks
{
    using System;
    using System.Text;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Testing.Threading;

    public class TaskDebugger : ITask
    {
        #region Fields

        private readonly TaskFactory _factory;
        private TestThreadType? _expectCalledBackOnType;
        private TestThreadType? _expectEndedOnType;
        private TestThreadType? _expectRanOnType;
        private TestThreadType? _expectStartedOnType;

        #endregion

        protected TaskDebugger(TaskFactory factory, string name, bool isAsync)
        {
            _factory = factory;
            Name = name;
            IsAsync = isAsync;
        }

        public TaskDebugger(TaskFactory factory, Action action, string name, bool isAsync)
            : this(
                factory, name, isAsync)
        {
            SetMethod(action);
        }

        protected ITask Task { get; private set; }

        public string Name { get; private set; }

        public bool IsAsync { get; private set; }

        public TestThreadInfo ActualRanOn { get; private set; }

        public TestThreadInfo ActualStartedOn { get; private set; }

        public TestThreadInfo ActualEndedOn { get; private set; }

        public TestThreadInfo ActualCalledBackOn { get; private set; }

        #region ITask Members

        public void ContinueFrom(IContinuationSource source)
        {
            Task.ContinueFrom(source);
        }

        public void Wait()
        {
            Task.Wait();
        }

        public void Start(TaskCallBack callBack, object userState)
        {
            Task.Start(r =>
               {
                   ActualCalledBackOn = TestThreadInfo.FromCurrentThread();
                   if (callBack != null)
                       callBack(this);
               }, userState);
        }

        public void ContinueFrom(IContinuationSource source, TaskCallBack callback, object userState)
        {
            Task.ContinueFrom(source, callback, userState);
        }

        public IContinuationSource ContinuationSource
        {
            get { return Task.ContinuationSource; }
        }

        public bool IsComplete
        {
            get { return ActualCalledBackOn != null; }
        }

        public Exception Error
        {
            get { return Task.Error; }
        }

        public bool IsCanceled
        {
            get { return Task.IsCanceled; }
        }

        public object AsyncState
        {
            get { return Task.AsyncState; }
        }

        #endregion

        protected void SetMethod<T>(Func<T> func)
        {
            // wrap action
            var actionWrapper = DelegateThreadDebugger.Debug("Running " + Name,
               () =>
               {
                   ActualRanOn = TestThreadInfo.FromCurrentThread();
                   return func();
               });
            if (IsAsync)
            {
                var beginActionWrapper = DelegateThreadDebugger.Debug
                   <AsyncCallback, object, IAsyncResult>(
                   "Starting " + Name,
                   (c, u) =>
                   {
                       ActualStartedOn = TestThreadInfo.FromCurrentThread();
                       return actionWrapper.BeginInvoke(c, u);
                   }
                   );
                var endActionWrapper = DelegateThreadDebugger.Debug<IAsyncResult, T>(
                   "Ending " + Name, (ar) =>
                      {
                          ActualEndedOn = TestThreadInfo.FromCurrentThread();
                          return actionWrapper.EndInvoke(ar);
                      });

                Task = _factory.FromAsync<T>(beginActionWrapper.Invoke, endActionWrapper.Invoke);
            }
            else
            {
                Action syncActionWrapper = () =>
                   {
                       ActualStartedOn = TestThreadInfo.FromCurrentThread();
                       actionWrapper();
                       ActualEndedOn = TestThreadInfo.FromCurrentThread();
                   };
                Task = _factory.From(syncActionWrapper);
            }
        }

        protected void SetMethod(Action action)
        {
            // wrap action
            var actionWrapper = DelegateThreadDebugger.Debug("Running " + Name,
               () =>
               {
                   ActualRanOn = TestThreadInfo.FromCurrentThread();
                   action();
               });
            if (IsAsync)
            {
                var beginActionWrapper = DelegateThreadDebugger.Debug
                   <AsyncCallback, object, IAsyncResult>(
                   "Starting " + Name,
                   (c, u) =>
                   {
                       ActualStartedOn = TestThreadInfo.FromCurrentThread();
                       return actionWrapper.BeginInvoke(c, u);
                   }
                   );
                var endActionWrapper = DelegateThreadDebugger.Debug<IAsyncResult>(
                   "Ending " + Name, (ar) =>
                      {
                          ActualEndedOn = TestThreadInfo.FromCurrentThread();
                          actionWrapper.EndInvoke(ar);
                      });

                Task = _factory.FromAsync(beginActionWrapper.Invoke, endActionWrapper.Invoke);
            }
            else
            {
                Action syncActionWrapper = () =>
                   {
                       ActualStartedOn = TestThreadInfo.FromCurrentThread();
                       actionWrapper();
                       ActualEndedOn = TestThreadInfo.FromCurrentThread();
                   };
                Task = _factory.From(syncActionWrapper);
            }
        }

        public void ExpectRanOn(TestThreadType expected)
        {
            _expectRanOnType = expected;
        }

        public void ExpectThreadSequence(TestThreadType? expectedStartOn, TestThreadType? expectedRanOn,
           TestThreadType? expectedEndedOn, TestThreadType? expectedCalledBackOn)
        {
            _expectStartedOnType = expectedStartOn;
            _expectRanOnType = expectedRanOn;
            _expectEndedOnType = expectedEndedOn;
            _expectCalledBackOnType = expectedCalledBackOn;
        }

        public void ExpectStartedOn(TestThreadType expected)
        {
            _expectStartedOnType = expected;
        }

        public void ExpectEndedOn(TestThreadType expected)
        {
            _expectEndedOnType = expected;
        }

        public void ExpectCalledBackOn(TestThreadType expected)
        {
            _expectCalledBackOnType = expected;
        }

        public void AssertCompleted()
        {
            Assert.IsTrue(IsComplete, "Task {0} is not complete.", Name);
            if (_expectStartedOnType != null)
                Assert.AreEqual(_expectStartedOnType.Value, ActualStartedOn.ThreadType, "StartOn");
            if (_expectRanOnType != null)
                Assert.AreEqual(_expectRanOnType.Value, ActualRanOn.ThreadType, "RanOn");
            if (_expectEndedOnType != null)
                Assert.AreEqual(_expectEndedOnType.Value, ActualEndedOn.ThreadType, "EndedOn");
            if (_expectCalledBackOnType != null)
                Assert.AreEqual(_expectCalledBackOnType.Value, ActualCalledBackOn.ThreadType, "CalledBackOn");
        }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendFormat("Task: {0}\r\n----------------------------------------------\r\n", Name)
               .AppendFormat("StartedOn={{\r\n  {0}\r\n}}\r\n", ActualStartedOn)
               .AppendFormat("RanOn={{\r\n  {0}\r\n}}\r\n", ActualRanOn)
               .AppendFormat("EndedOn={{\r\n  {0}\r\n}}\r\n", ActualEndedOn)
               .AppendFormat("ActualCalledBackOn={{\r\n  {0}\r\n}}\r\n", ActualCalledBackOn);
            return sb.ToString();
        }
    }

    public class TaskDebugger<T> : TaskDebugger, ITask<T>
    {
        public TaskDebugger(TaskFactory factory, Func<T> func, string name, bool isAsync)
            : base(
               factory, name, isAsync)
        {
            SetMethod(func);
        }

        #region ITask<T> Members

        public T Result
        {
            get { return ((ITask<T>)Task).Result; }
        }

        public void Start(TaskCallBack<T> callBack, object asyncState)
        {
            TaskCallBack cb = c => { if (callBack != null) callBack(this); };
            Start(cb, asyncState);
        }

        #endregion
    }
}